/*
* Copyright (C) Microsoft. All rights reserved.  
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not 
* use this file except in compliance with the License.  You may obtain a copy 
* of the License at http://www.apache.org/licenses/LICENSE-2.0  
* 
* THIS CODE IS PROVIDED *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED 
* WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, 
* MERCHANTABLITY OR NON-INFRINGEMENT. 
*
* See the Apache License, Version 2.0 for specific language governing 
* permissions and limitations under the License.
*
*/
#pragma once

#include <baja/common.hpp>
#include <baja/observable.hpp>
#include <journal/models/interfaces.h>
#include <journal/views/interfaces.h>
#include <journal/common/settings_service.hpp>
#include <dependencies/includes/ink_renderer_interface.h>

namespace journal { namespace view {

namespace b = baja;
namespace bi = baja::ink;
namespace g = baja::graphics;

class ink_surface : 
    public iink_surface,
    public model::iink_surface_model_observer,
    public std::enable_shared_from_this<iink_surface>,
    public iink_surface_automation
{
private:
    enum class state
    {
        idle,
        drawing,
        deleting
    };

    class ink_configuration :
        public bi::iink_renderer_configuration
    {
    public:
        b::uint32 maxMemoryVertexCount()
        {
            return constants::ink::maxMemoryVertexCount();
        }
        b::uint32 maxMemoryVertexCountExpansionRatio()
        {
            return constants::ink::maxMemoryVertexCountExpansionRatio();
        }
        b::uint32 maxVertexCount()
        {
            return constants::ink::maxVertexCount();
        }
        b::uint32 slicesForRoundStamp()
        {
            return constants::ink::slicesForRoundStamp();
        }
        b::float32 smoothingInterval()
        {
            return constants::ink::smoothingInterval();
        }
        b::Boolean smoothVectorized()
        {
            return constants::ink::smoothVectorized();
        }
        b::Boolean smooth()
        {
            return constants::ink::smooth();
        }
        b::Boolean debuggingMode()
        {
            return constants::ink::debuggingMode();
        }
    };

public:
    ink_surface(
        const std::shared_ptr<model::iink_surface_model>& model,
        const std::shared_ptr<a::iinput_capture>& inputCapture
        );

    ~ink_surface() { this->cleanup(); }
    static std::shared_ptr<ink_surface> create(
        const std::shared_ptr<model::iink_surface_model>& model,
        const std::shared_ptr<a::iinput_capture>& inputCapture
        );

    // iink_surface
    std::shared_ptr<model::iink_surface_model> model() { return _inkSurfaceModel; }
    b::int32 addObserver(const std::shared_ptr<view::iink_surface_observer>& observer) { return _observerSource += observer; }
    void removeObserver(const std::shared_ptr<view::iink_surface_observer>& observer) { _observerSource -= observer; }
    void removeObserver(const b::int32 cookie) { _observerSource -= cookie; }
    void removeInkStroke(b::uint32 strokeId);
    void setRenderTarget(const std::shared_ptr<g::render_target>& renderTarget);

    // icached_render_object
    bool shouldRebuildCache() { return _rebuildCache; }
    void setRebuildCache(const bool value)
    {
        _rebuildCache = value;
        if (value)
        {
            _blockWatermark = -1;
            _bundleWatermark = 0;
        }
    }
    void rebuildRenderData();
    bool renderToCache();
    void renderToIdsCache();
    bool getDynamicBounds(m::rectangle* bounds);
    void renderDynamic();
    void selectTool(ink_surface_tool tool);

    // idefered_render_object
    void onRenderDeferred();

    // iinput_observer
    void onPointer(const a::pointer_args& args, void* context = nullptr);
    void onGesture(const a::gesture_args& args, void* context = nullptr) { /* noop */ }
    void onKey(const a::key_args& args, void* context = nullptr) { /* noop */ }

    // model::iink_surface_model_observer
    void ink_surface::onInkSurfaceModelReady(const std::shared_ptr<model::iink_surface_model>& inkSurface);
    void onStrokeModelAttached(const std::shared_ptr<model::iink_stroke_model>& newStroke);
    void onStrokeModelReady(const std::shared_ptr<model::iink_stroke_model>& newStroke);
    void onStrokeModelRemoved(const std::shared_ptr<model::iink_stroke_model>& stroke);
    void onInkSurfaceChildrenReady(const std::shared_ptr<model::iink_surface_model>& inkSurface);

    // iink_surface_automation
    void generateInk(const std::shared_ptr<model::iink_stroke_model>& newStroke);

private:
    void init();
    void cleanup();
    void finishCurrentStroke();
    bool renderToCacheInternal(const bool deferred);
    void raiseDirty()
    {
        _observerSource.raise(
            [&](const std::shared_ptr<view::iink_surface_observer>& observer, void* context)
            {
                observer->onDirty();
            });
    }

    void localToIdsMap(
        const page_pointer_context& ppc,
        b::uint32& x,
        b::uint32& y);

    bool detectAndRemoveStrokes(
        const page_pointer_context& ppc);

    void regenerateInk();

private:
    state _state;
    std::weak_ptr<a::iinput_capture> _inputCapture;
    std::shared_ptr<model::iink_surface_model> _inkSurfaceModel;
    b::observable_source<view::iink_surface_observer> _observerSource;
    b::int32 _surfaceObserverCookie;

    b::int32 _blockWatermark;
    b::int32 _bundleWatermark;

    bool _rebuildCache;
    a::device_type _currentDeviceType;
    std::shared_ptr<model::iink_stroke_model> _currentStrokeModel;
    std::shared_ptr<bi::iink_renderer> _dynamicStrokeInkRenderer; // renderer for current stroke
    std::shared_ptr<bi::iink_ids_map> _inkIdsMap;
    ink_surface_tool _currentTool;

    std::shared_ptr<g::render_target> _renderTarget;

    std::shared_ptr<bi::iink_data> _generatedInk;
};

std::shared_ptr<iink_surface> createInkSurface(
    const std::shared_ptr<model::iink_surface_model>& model,
    const std::shared_ptr<a::iinput_capture>& inputCapture
    );

}} // namespace journal::view